from spark import GenericScanner, GenericParser, GenericASTTraversal
from hashdb_config import ConfigSettings

class Token(object):
    def __init__(self, type, attr):
        object.__init__(self)

        self.type = type
        self.attr = attr

    def __iter__(self):
        if False:
            yield None

    def __repr__(self):
        return 'Token(type=%r, attr=%r)' % (self.type, self.attr)

class AST(object):
    def __init__(self, type, args):
        object.__init__(self)

        self.type = type
        self.args = args

    def _iterstr(self, node, padd=0):
        if isinstance(node, AST):
            yield '%s%s' % (' '*padd, node.type)
            for arg in node.args:
                for y in self._iterstr(arg, padd+2):
                    yield y
        else:
            yield '%s%s' % (' '*padd, node)

    def __str__(self):
        return '\n'.join(self._iterstr(self))

    def __iter__(self):
        return self.args.__iter__()

class AST_setting(AST):
    def __init__(self, name, value, type='setting'):
        AST.__init__(self, type, [name, value])

    @property
    def name(self):
        return self.args[0].attr
    @name.setter
    def name(self, v):
        self.args[0].attr = v

    @property
    def value(self):
        return self.args[1].attr
    @value.setter
    def value(self, v):
        self.args[1].attr = v

class AST_settings(AST):
    def __init__(self, settings, type='settings'):
        AST.__init__(self, type, args=settings)

    @property
    def settings(self):
        return self.args
    @settings.setter
    def settings(self, v):
        self.args = v
#
# For formatting/grammar information,
# See: http://manpages.ubuntu.com/manpages/lucid/man5/updatedb.conf.5.html
#
class UpdateDBScanner(GenericScanner):
    r"""

    Tokenizes an updatedb config file

    >>> cfg = '''
    ... PRUNE_BIND_MOUNTS="yes"
    ... # PRUNENAMES=".git .bzr .hg .svn"
    ... PRUNEPATHS="/tmp /var/spool /media"
    ... PRUNEFS="NFS nfs nfs4 rpc_pipefs afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre_lite tmpfs usbfs udf fuse.glusterfs fuse.sshfs ecryptfs fusesmb devtmpfs"
    ... '''
    >>> scn = UpdateDBScanner()
    >>> for token in scn.tokenize(cfg):
    ...     print token
    Token(type='name', attr='PRUNE_BIND_MOUNTS')
    Token(type='=', attr=None)
    Token(type='value', attr='yes')
    Token(type='name', attr='PRUNEPATHS')
    Token(type='=', attr=None)
    Token(type='value', attr='/tmp /var/spool /media')
    Token(type='name', attr='PRUNEFS')
    Token(type='=', attr=None)
    Token(type='value', attr='NFS nfs nfs4 rpc_pipefs afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre_lite tmpfs usbfs udf fuse.glusterfs fuse.sshfs ecryptfs fusesmb devtmpfs')
    """
    def __init__(self):
        GenericScanner.__init__(self)

    def tokenize(self, input):
        self.rv = []
        GenericScanner.tokenize(self, input)
        return self.rv

    def t_whitespace(self, s):
        r' (\s|\r|\n)+ '
        pass

    def t_comment(self, s):
        r' \#.* '
        pass

    def t_name(self, s):
        r' (PRUNEFS|PRUNENAMES|PRUNEPATHS|PRUNE_BIND_MOUNTS) '
        self.rv.append(Token(type='name', attr=s))

    def t_equals(self, s):
        r' = '
        self.rv.append(Token(type='=', attr=None))

    def t_value(self, s):
        r' "[^"\r\n]*" '
        self.rv.append(Token(type='value', attr=s[1:-1]))

class UpdateDBParser(GenericParser):
    r"""

    Generates an AST tree from a tokenized updatedb config file

    >>> cfg = '''
    ... PRUNE_BIND_MOUNTS="yes"
    ... # PRUNENAMES=".git .bzr .hg .svn"
    ... PRUNEPATHS="/tmp /var/spool /media"
    ... PRUNEFS="NFS nfs nfs4 rpc_pipefs afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre_lite tmpfs usbfs udf fuse.glusterfs fuse.sshfs ecryptfs fusesmb devtmpfs"
    ... '''
    >>> scn = UpdateDBScanner()
    >>> prs = UpdateDBParser()
    >>> print prs.parse(scn.tokenize(cfg))
    settings
      setting
        Token(type='name', attr='PRUNE_BIND_MOUNTS')
        Token(type='value', attr='yes')
      setting
        Token(type='name', attr='PRUNEPATHS')
        Token(type='value', attr='/tmp /var/spool /media')
      setting
        Token(type='name', attr='PRUNEFS')
        Token(type='value', attr='NFS nfs nfs4 rpc_pipefs afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre_lite tmpfs usbfs udf fuse.glusterfs fuse.sshfs ecryptfs fusesmb devtmpfs')
    """

    def __init__(self, start='settings'):
        GenericParser.__init__(self, start)

    def typestring(self, token):
		return token.type

    def p_settings_1(self, args):
        '''
        settings ::= setting settings
        '''
        return AST_settings(settings=[args[0]] + args[1].args)

    def p_settings_2(self, args):
        '''
        settings ::= setting
        '''
        return AST_settings(settings=[args[0]])

    def p_settings_3(self, args):
        '''
        settings ::=
        '''
        return AST_settings(settings=[])

    def p_setting(self, args):
        '''
        setting ::= name = value
        '''
        return AST_setting(name=args[0], value=args[2])


class ValueCheck(GenericASTTraversal):
    def __init__(self, ast):
        GenericASTTraversal.__init__(self, ast)
        self.postorder()

    def n_setting(self, node):
        if node.name == 'PRUNE_BIND_MOUNTS':
            # boolean
            if node.value in ['0', 'no']:
                node.value = False
            elif node.value in ['1', 'yes']:
                node.value = True
            else:
                raise SystemExit
        else:
            # space seperated list
            node.value = node.value.split()

def parse(text):
    """

    Returns a ConfigSettings object containing all the relevent information gathered from the config specified by text

    >>> cfg = '''
    ... PRUNE_BIND_MOUNTS="yes"
    ... # PRUNENAMES=".git .bzr .hg .svn"
    ... PRUNEPATHS="/tmp /var/spool /media"
    ... PRUNEFS="NFS nfs nfs4 rpc_pipefs afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre_lite tmpfs usbfs udf fuse.glusterfs fuse.sshfs ecryptfs fusesmb devtmpfs"
    ... '''
    >>> parse(cfg)
    ConfigSettings(
        PRUNEFS             = [],
        PRUNENAMES          = [],
        PRUNEPATHS          = [],
        PRUNE_BIND_MOUNTS   = False
    ).update({
        'PRUNEFS'           : ['NFS', 'nfs', 'nfs4', 'rpc_pipefs', 'afs', 'binfmt_misc', 'proc', 'smbfs', 'autofs', 'iso9660', 'ncpfs', 'coda', 'devpts', 'ftpfs', 'devfs', 'mfs', 'shfs', 'sysfs', 'cifs', 'lustre_lite', 'tmpfs', 'usbfs', 'udf', 'fuse.glusterfs', 'fuse.sshfs', 'ecryptfs', 'fusesmb', 'devtmpfs'],
        'PRUNEPATHS'        : ['/tmp', '/var/spool', '/media'],
        'PRUNE_BIND_MOUNTS' : True
        },
        context=None
    )
    #   PRUNEFS             = ['NFS', 'nfs', 'nfs4', 'rpc_pipefs', 'afs', 'binfmt_misc', 'proc', 'smbfs', 'autofs', 'iso9660', 'ncpfs', 'coda', 'devpts', 'ftpfs', 'devfs', 'mfs', 'shfs', 'sysfs', 'cifs', 'lustre_lite', 'tmpfs', 'usbfs', 'udf', 'fuse.glusterfs', 'fuse.sshfs', 'ecryptfs', 'fusesmb', 'devtmpfs']
    #   PRUNENAMES          = []
    #   PRUNEPATHS          = ['/tmp', '/var/spool', '/media']
    #   PRUNE_BIND_MOUNTS   = True
    """

    scanner = UpdateDBScanner()
    tokens  = scanner.tokenize(text)
    parser  = UpdateDBParser()
    ast     = parser.parse(tokens)
    ValueCheck(ast)

    config = ConfigSettings(
        PRUNE_BIND_MOUNTS = False,
        PRUNENAMES = [],
        PRUNEPATHS = [],
        PRUNEFS = []
    )

    for setting in ast:
        config.set(setting.name, setting.value)

    return config


if __name__ == '__main__':
    import doctest
    doctest.testmod()